﻿/*
    a04_mines - com.gitee.zcatt.mines

    挖地雷游戏， 用于展示基本的gtk widget编程。
    包括，
        .GtkBuilder载入ui
        .GtkAspectFrame自适应ratio
        .style provider和css
        .modal GtkDialog 
        .signal regist, connect, and emit
        .gtk/menus.ui

    derived from gnome-mines.

*/

#include <gtk/gtk.h>
#include "mines.h"
#include "mine_view.h"

enum 
{
    Board_0,
    Board_small,
    Board_medium,
    Board_big,
    Board_custom,
};

typedef struct {
    GtkApplication app;
    
    gint board_type;         //use Board_XXX
    gint row_count;
    gint col_count;
    gint mine_count;
#if 0    
    GSettings *settings;
    GSimpleAction *new_game_act;
#endif

    GtkWindow *window;
    GtkLabel *remain_lbl;
    GtkLabel *used_sec_lbl;
    MineView *mine_view;
    GtkImage *button_image;
    GtkDialog *config_custom_diag;
    GtkSpinButton *row_spin_btn;
    GtkSpinButton *col_spin_btn;
    GtkSpinButton *mines_spin_btn;
} Mines;

typedef GtkApplicationClass MinesClass;

G_DEFINE_TYPE(Mines, mines, GTK_TYPE_APPLICATION)

static void mines_finalize(GObject *obj)
{
    printf("mines_finalize()......\n");

    Mines *mines = G_TYPE_CHECK_INSTANCE_CAST(obj, mines_get_type(), Mines);
    GObject* p;
    p = G_OBJECT(mines->config_custom_diag);
    printf("diag ref=%d\n", p->ref_count);
    p = G_OBJECT(mines->window);
    printf("window ref=%d\n", p->ref_count);

    gtk_widget_destroy((GtkWidget*)mines->config_custom_diag);
#if 0    
    g_object_unref(mines->settings);
    g_object_unref(mines->new_game_act);
#endif
    g_object_unref(mines->window);

    G_OBJECT_CLASS(mines_parent_class)->finalize(obj);
}

static void set_board_type(Mines *mines, gint board_type, gint row_count, gint col_count, gint mine_count)
{
    switch(board_type)
    {
        case Board_small:
            mines->board_type = Board_small;
            mines->row_count = 8;
            mines->col_count = 8;
            mines->mine_count = 10;
            break;
        case Board_medium:
            mines->board_type = Board_medium;
            mines->row_count = 16;
            mines->col_count = 16;
            mines->mine_count = 40;
            break;
        case Board_big:
            mines->board_type = Board_big;
            mines->row_count = 16;
            mines->col_count = 30;
            mines->mine_count = 99;
            break;
        case Board_custom:
            mines->board_type = Board_custom;
            mines->row_count = row_count;
            mines->col_count = col_count;
            mines->mine_count = mine_count;
            break;
        case Board_0:
        default:
            g_assert(FALSE);
            break;
    }
}

static gint mines_handle_local_options(GApplication *app, GVariantDict *options)
{
    printf("mines_handle_loal_options()......\n");
    Mines *mines = (Mines*) app;
    g_assert(options);   //skip if options is null    

    gint res = -1;     //go on

    if(g_variant_dict_contains(options, "version"))
    {
        fprintf(stderr, "%1s %2s\n", "Mines", MinesVersion);
        res = EXIT_SUCCESS;     //exit
    }
    else if(g_variant_dict_contains(options, "small"))
    {
        set_board_type(mines, Board_small, 0,0,0);
    }
    else if(g_variant_dict_contains(options, "medium"))
    {
        set_board_type(mines, Board_medium, 0,0,0);
    }
    else if(g_variant_dict_contains(options, "big"))
    {
        set_board_type(mines, Board_big, 0,0,0);
    }

    printf("boardType=%d\n", mines->board_type);
    return res;
}




static void new_game_cb(GSimpleAction *action, GVariant *paran, gpointer user_data)
{
    Mines *mines = user_data;
    mine_view_start_game(mines->mine_view, mines->row_count, mines->col_count, mines->mine_count);
}

static void score_rank_cb(GSimpleAction *action, GVariant *paran, gpointer user_data)
{
    
}

static void about_cb(GSimpleAction *action, GVariant *paran, gpointer user_data)
{
  gtk_show_about_dialog (NULL,
                         "program-name", "Mines",
                         "title", "About Mines",
                         "comments", "Demo gtk programming. Derived from gnome-mines.",
                         "date","June2021",
                         NULL);
    
}

static void quit_cb(GSimpleAction *action, GVariant *paran, gpointer user_data)
{
    Mines *mines = (Mines*) user_data;
    gtk_widget_destroy((GtkWidget*)mines->window);    
    //g_application_quit((GApplication*)mines);
}

#if 0
static void activate_radio_cb (GSimpleAction *action,
                                GVariant      *parameter,
                                gpointer       user_data)
{
    g_action_change_state (G_ACTION (action), parameter);
}


static void change_board_type_cb (GSimpleAction *action,
                                    GVariant      *state,
                                    gpointer       user_data)
{
    Mines *mines = user_data;
    const gchar* type = g_variant_get_string(state, NULL);
    if(g_str_equal(type, "small"))
    {
        printf("small boadtype\n");
    }
    else if(g_str_equal(type, "medium"))
    {
        printf("medium boadtype\n");
        
    }
    else if(g_str_equal(type, "big"))
    {
        printf("big boadtype\n");        
    }

    g_simple_action_set_state (action, state);
}
#endif

static void board_type_small_cb(GSimpleAction *action, GVariant *paran, gpointer user_data)
{
    Mines *mines = (Mines*) user_data;
    set_board_type(mines, Board_small, 0, 0, 0);
    mine_view_force_start_game(mines->mine_view, mines->row_count, mines->col_count, mines->mine_count);
}
static void board_type_medium_cb(GSimpleAction *action, GVariant *paran, gpointer user_data)
{
    Mines *mines = (Mines*) user_data;
    set_board_type(mines, Board_medium, 0, 0, 0);
    mine_view_force_start_game(mines->mine_view, mines->row_count, mines->col_count, mines->mine_count);
}

static void board_type_big_cb(GSimpleAction *action, GVariant *paran, gpointer user_data)
{
    Mines *mines = (Mines*) user_data;
    set_board_type(mines, Board_big, 0, 0, 0);
    mine_view_force_start_game(mines->mine_view, mines->row_count, mines->col_count, mines->mine_count);
}

static void board_type_custom_cb(GSimpleAction *action, GVariant *paran, gpointer user_data)
{
    Mines *mines = (Mines*) user_data;
    g_assert(mines->config_custom_diag);

    gtk_spin_button_set_range(mines->col_spin_btn, 8, 30);
    gtk_spin_button_set_increments(mines->col_spin_btn, 1, 1);
    gtk_spin_button_set_value(mines->col_spin_btn, mines->col_count);

    gtk_spin_button_set_range(mines->row_spin_btn, 8, 16);
    gtk_spin_button_set_increments(mines->row_spin_btn, 1, 1);
    gtk_spin_button_set_value(mines->row_spin_btn, mines->col_count);
 
    gtk_spin_button_set_range(mines->mines_spin_btn, 1, 50);
    gtk_spin_button_set_increments(mines->mines_spin_btn, 1, 1);
    gtk_spin_button_set_value(mines->mines_spin_btn, (gint)(mines->mine_count*100.0/mines->row_count/mines->col_count));

    printf("show custom\n");
    gtk_widget_show((GtkWidget*)mines->config_custom_diag);
    printf("show custom over\n");

#if 0
    set_board_type(mines, Board_big, 0, 0, 0);
    mine_view_force_start_game(mines->mine_view, mines->row_count, mines->col_count, mines->mine_count);
#endif    
}

static void close_custom_diag_cb(GtkWidget *diag, gint response, Mines *mines)
{
    gtk_widget_hide(diag);

    if(response == GTK_RESPONSE_CANCEL)
        return;

    int row_count = gtk_spin_button_get_value(mines->row_spin_btn);
    int col_count = gtk_spin_button_get_value(mines->col_spin_btn);
    int mines_count = gtk_spin_button_get_value(mines->mines_spin_btn);
    set_board_type(mines, Board_custom, row_count, col_count, mines_count);
    mine_view_force_start_game(mines->mine_view, mines->row_count, mines->col_count, mines->mine_count);    
}


static void game_over_cb(MineView *view, gboolean game_fail, Mines *mines)
{
    gtk_image_set_from_resource(mines->button_image, game_fail ? "/com/gitee/zcatt/mines/assets/face-sad.png" 
                                            : "/com/gitee/zcatt/mines/assets/face-smile.png");
}

static void game_started_cb(MineView *view, Mines *mines)
{
    gtk_image_set_from_resource(mines->button_image, "/com/gitee/zcatt/mines/assets/face-plain.png");
}

static void game_flag_cb(MineView *view, gint remain_mines, Mines *mines)
{
    gchar *s = g_strdup_printf("%03d", remain_mines);
    gtk_label_set_label(mines->remain_lbl, s);
    g_free(s);
}

static void game_time_cb(MineView *view, gint used_sec, Mines *mines)
{
    gchar *s = g_strdup_printf("%03d", used_sec);
    gtk_label_set_label(mines->used_sec_lbl, s);
    g_free(s);
}

static gboolean first_draw = FALSE;
static gboolean mine_view_draw_cb(GtkWidget *widget, cairo_t *cr, Mines *mines)
{
    if(!first_draw)
    {
        gtk_widget_queue_resize(widget);
        gtk_widget_queue_draw((GtkWidget*)mines->mine_view);
        first_draw = TRUE;
        return TRUE;
    }

    return FALSE;
}

static void aspect_size_allocate_cb(GtkWidget *widget, GtkAllocation *allocation, Mines *mines)
{
    gfloat ratio;
    g_object_get(widget, "ratio", &ratio, NULL);
    gfloat new_ratio = (gfloat)mines->col_count/mines->row_count;
    if(new_ratio != ratio)
    {
        g_object_set(widget, "ratio", new_ratio, NULL);
        first_draw = FALSE;
    }

}

static GActionEntry app_entries[]=
{
    {"new-game", new_game_cb, NULL, NULL, NULL}
    , {"score-rank", score_rank_cb, NULL, NULL, NULL}
    , {"about", about_cb, NULL, NULL, NULL}
    , {"quit", quit_cb, NULL, NULL, NULL}
    , {"board-type-small", board_type_small_cb, NULL, NULL, NULL}
    , {"board-type-medium", board_type_medium_cb, NULL, NULL, NULL}
    , {"board-type-big", board_type_big_cb, NULL, NULL, NULL}
    , {"board-type-custom", board_type_custom_cb, NULL, NULL, NULL}
};

static void mines_startup(GApplication *app)
{
    printf("mines_startup()......\n");
    G_APPLICATION_CLASS(mines_parent_class)->startup(app);
    g_set_application_name("Mines");

    Mines *mines = (Mines*) app;
    g_assert(mines);

    //read settings

   //set accel
#if 0               //set in menus.ui   
    struct {
        const gchar* action_target;
        const gchar* accel_keys[2];
    } accels[]= {
        {"app.quit", {"<Ctrl>q", NULL}},
        {"app.new-game", {"<Ctrl>n", NULL}},
        {"app.about", {"<Ctrl>a", NULL}}
    };

    for(int i = 0; i< G_N_ELEMENTS(accels); i++)
        gtk_application_set_accels_for_action((GtkApplication*)mines, accels[i].action_target, accels[i].accel_keys);
#endif

#if 0    
    mines->settings = g_settings_new(MinesAppId);
#else
    if(mines->board_type == Board_0)    
        set_board_type(mines, Board_small, 0, 0, 0);
#endif

    //add actions
    g_action_map_add_action_entries((GActionMap*)app, app_entries, (gint)G_N_ELEMENTS(app_entries), mines);

#if 0    
    mines->new_game_act =  G_SIMPLE_ACTION(g_action_map_lookup_action((GActionMap*)app, "new-game"));
    g_assert(mines->new_game_act);
    g_object_ref(mines->new_game_act);
    g_simple_action_set_enabled(mines->new_game_act, TRUE);
#endif

    //load css
    GtkStyleProvider *provider;
    provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ());
    gtk_css_provider_load_from_resource (GTK_CSS_PROVIDER (provider), "/com/gitee/zcatt/mines/mines.css");
    GdkScreen *scr = gdk_screen_get_default();
    gtk_style_context_add_provider_for_screen(scr, provider, (guint) GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    g_object_unref(provider);

    //load ui
    GtkBuilder *builder = gtk_builder_new_from_resource("/com/gitee/zcatt/mines/interface.ui");


    mines->remain_lbl = GTK_LABEL(gtk_builder_get_object(builder, "remain_count_label"));
    mines->used_sec_lbl = GTK_LABEL(gtk_builder_get_object(builder, "used_sec_label"));
    
    mines->button_image = GTK_IMAGE(gtk_builder_get_object(builder, "game_state_img"));

    mines->config_custom_diag = GTK_DIALOG(gtk_builder_get_object(builder, "custom_diag"));
    g_signal_connect(mines->config_custom_diag, "response", G_CALLBACK(close_custom_diag_cb), mines);

    GtkSpinButton *sb = GTK_SPIN_BUTTON(gtk_builder_get_object(builder,"width_spin_btn"));
    mines->col_spin_btn = sb;

    sb = GTK_SPIN_BUTTON(gtk_builder_get_object(builder,"height_spin_btn"));
    mines->row_spin_btn = sb;

    sb = GTK_SPIN_BUTTON(gtk_builder_get_object(builder,"mines_spin_btn"));
    mines->mines_spin_btn = sb;
  

    GtkScrolledWindow *scrolled = GTK_SCROLLED_WINDOW(gtk_builder_get_object(builder, "scrolled"));
    gtk_style_context_add_class(gtk_widget_get_style_context((GtkWidget*)scrolled), "scrolled");

    GtkWidget *mv = mine_view_new();
    mines->mine_view = (MineView*) mv;
    //mine_view_set_size((MineView*)mv, mines->row_count, mines->col_count, mines->mine_count);
    gtk_widget_show((GtkWidget*)mv);
    gtk_container_add(GTK_CONTAINER(scrolled), mv);

    g_signal_connect_object(mv, "draw", (GCallback)mine_view_draw_cb, mines, 0);
    g_signal_connect_object(mv, "game-over", (GCallback)game_over_cb, mines, 0);
    g_signal_connect_object(mv, "game-started", (GCallback)game_started_cb, mines, 0);
    g_signal_connect_object(mv, "game-flag", (GCallback)game_flag_cb, mines, 0);
    g_signal_connect_object(mv, "game-time", (GCallback)game_time_cb, mines, 0);

    //enable aspect adaptive ratio 
    GtkWidget *aspect = (GtkWidget*) gtk_builder_get_object(builder, "mine_view_aspect");
    g_signal_connect_object(aspect, "size-allocate", (GCallback)aspect_size_allocate_cb, mines, 0);


    //trig game-flag and game-time
    mine_view_start_game((MineView*)mv, mines->row_count, mines->col_count, mines->mine_count);


    mines->window = GTK_WINDOW(gtk_builder_get_object(builder, "main_window"));
    g_object_ref(mines->window);        //unref at mines_finalize()

    //gtk_window_set_default_size(mines->window, 600, 400);
    gtk_application_add_window((GtkApplication*)mines, mines->window);

#if 0
    GtkHeaderBar *hb = (GtkHeaderBar*) gtk_header_bar_new();
    gtk_header_bar_set_show_close_button(hb, TRUE);
    gtk_header_bar_set_title(hb, "MinesApp");
    gtk_widget_show((GtkWidget*)hb);

    gtk_window_set_titlebar(mines->window, (GtkWidget*)hb);
#else
    gtk_window_set_title(mines->window, "MinesApp");
#endif

 
    //set menu
#if 1       //load menu

#else       //hard-code menu
    GMenu *menu= g_menu_new();
    GMenu *mines_menu = g_menu_new();
    GMenu *help_menu = g_menu_new();
    g_menu_append_submenu(menu, "_Mines", (GMenuModel*)mines_menu);
    g_menu_append_submenu(menu, "_Help", (GMenuModel*)help_menu);
    g_menu_append(mines_menu, "_New game", "app.new-game");
    g_menu_append(mines_menu, "_Scores", "app.score-rank");
    g_menu_append(mines_menu, "_Quit", "app.quit");
    g_menu_append(help_menu, "_About", "app.about");

    gtk_application_set_menubar((GtkApplication*)mines, (GMenuModel*)menu);
    g_object_unref(menu);
    g_object_unref(mines_menu);
    g_object_unref(help_menu);
#endif

    g_object_unref(builder);

}


static void mines_shutdown(GApplication *app)
{
    printf("mines_shutdown()......\n");
    G_APPLICATION_CLASS(mines_parent_class)->shutdown(app);

    //Mines *mines = (Mines*) app;

    //save settings
#if 0    
    if(mines->settings)
        g_object_unref(mines->settings);
#endif
}


static void mines_activate(GApplication *app)
{
    printf("mines_activate()......\n");
    Mines *mines = (Mines*) app;
    gtk_widget_show_all(GTK_WIDGET(mines->window));
}

static void mines_init(Mines *app)
{
    printf("mines_init()......\n");

}


static void mines_class_init(MinesClass *clazz)
{
    printf("mines_class_init()......\n");

    GApplicationClass *appCls = G_APPLICATION_CLASS(clazz);
    g_assert(appCls);
    appCls->handle_local_options = mines_handle_local_options;
    appCls->startup = mines_startup;
    appCls->shutdown = mines_shutdown;
    appCls->activate = mines_activate;
    G_OBJECT_CLASS(clazz)->finalize = mines_finalize;
}


static const GOptionEntry optionEntries[] = 
{
    {"version", 'v', 0, G_OPTION_ARG_NONE, NULL, "Print version.", NULL}
    , {"small", 's', 0, G_OPTION_ARG_NONE, NULL, "Small board.", NULL}
    , {"medium", 'm', 0, G_OPTION_ARG_NONE, NULL, "Medium board.", NULL}
    , {"big", 'b', 0, G_OPTION_ARG_NONE, NULL, "Big board.", NULL}
    , {NULL}
};

Mines* mines_new(void)
{
    Mines *app;

    app = g_object_new(mines_get_type()
                        , "application-id", MinesAppId
                        , "flags", G_APPLICATION_NON_UNIQUE
                        , NULL);
    g_application_add_main_option_entries(G_APPLICATION(app), optionEntries);

    return app;
}


int main(int argc, char** argv)
{
    int res;
    Mines *mines;

    mines = mines_new();
    res = g_application_run(G_APPLICATION(mines), argc, argv);
    g_object_unref(mines);

    return res;
}

